home *** CD-ROM | disk | FTP | other *** search
/ Ultra Pack / UltraComputing Partner Applications.iso / SunLabs / tclTK / src / tk4.0 / tkMessage.c < prev    next >
C/C++ Source or Header  |  1995-06-09  |  28KB  |  907 lines

  1. /* 
  2.  * tkMessage.c --
  3.  *
  4.  *    This module implements a message widgets for the Tk
  5.  *    toolkit.  A message widget displays a multi-line string
  6.  *    in a window according to a particular aspect ratio.
  7.  *
  8.  * Copyright (c) 1990-1994 The Regents of the University of California.
  9.  * Copyright (c) 1994-1995 Sun Microsystems, Inc.
  10.  *
  11.  * See the file "license.terms" for information on usage and redistribution
  12.  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  13.  */
  14.  
  15. static char sccsid[] = "@(#) tkMessage.c 1.60 95/06/09 08:20:03";
  16.  
  17. #include "tkPort.h"
  18. #include "default.h"
  19. #include "tkInt.h"
  20.  
  21. /*
  22.  * A data structure of the following type is kept for each message
  23.  * widget managed by this file:
  24.  */
  25.  
  26. typedef struct {
  27.     Tk_Window tkwin;        /* Window that embodies the message.  NULL
  28.                  * means that the window has been destroyed
  29.                  * but the data structures haven't yet been
  30.                  * cleaned up.*/
  31.     Display *display;        /* Display containing widget.  Used, among
  32.                  * other things, so that resources can be
  33.                  * freed even after tkwin has gone away. */
  34.     Tcl_Interp *interp;        /* Interpreter associated with message. */
  35.     Tcl_Command widgetCmd;    /* Token for message's widget command. */
  36.     Tk_Uid string;        /* String displayed in message. */
  37.     int numChars;        /* Number of characters in string, not
  38.                  * including terminating NULL character. */
  39.     char *textVarName;        /* Name of variable (malloc'ed) or NULL.
  40.                  * If non-NULL, message displays the contents
  41.                  * of this variable. */
  42.  
  43.     /*
  44.      * Information used when displaying widget:
  45.      */
  46.  
  47.     Tk_3DBorder border;        /* Structure used to draw 3-D border and
  48.                  * background.  NULL means a border hasn't
  49.                  * been created yet. */
  50.     int borderWidth;        /* Width of border. */
  51.     int relief;            /* 3-D effect: TK_RELIEF_RAISED, etc. */
  52.     int highlightWidth;        /* Width in pixels of highlight to draw
  53.                  * around widget when it has the focus.
  54.                  * <= 0 means don't draw a highlight. */
  55.     XColor *highlightBgColorPtr;
  56.                 /* Color for drawing traversal highlight
  57.                  * area when highlight is off. */
  58.     XColor *highlightColorPtr;    /* Color for drawing traversal highlight. */
  59.     int inset;            /* Total width of all borders, including
  60.                  * traversal highlight and 3-D border.
  61.                  * Indicates how much interior stuff must
  62.                  * be offset from outside edges to leave
  63.                  * room for borders. */
  64.     XFontStruct *fontPtr;    /* Information about text font, or NULL. */
  65.     XColor *fgColorPtr;        /* Foreground color in normal mode. */
  66.     GC textGC;            /* GC for drawing text in normal mode. */
  67.     int padX, padY;        /* User-requested extra space around text. */
  68.     Tk_Anchor anchor;        /* Where to position text within window region
  69.                  * if window is larger or smaller than
  70.                  * needed. */
  71.     int width;            /* User-requested width, in pixels.  0 means
  72.                  * compute width using aspect ratio below. */
  73.     int aspect;            /* Desired aspect ratio for window
  74.                  * (100*width/height). */
  75.     int lineLength;        /* Length of each line, in pixels.  Computed
  76.                  * from width and/or aspect. */
  77.     int msgHeight;        /* Total number of pixels in vertical direction
  78.                  * needed to display message. */
  79.     Tk_Justify justify;        /* Justification for text. */
  80.  
  81.     /*
  82.      * Miscellaneous information:
  83.      */
  84.  
  85.     Cursor cursor;        /* Current cursor for window, or None. */
  86.     char *takeFocus;        /* Value of -takefocus option;  not used in
  87.                  * the C code, but used by keyboard traversal
  88.                  * scripts.  Malloc'ed, but may be NULL. */
  89.     int flags;            /* Various flags;  see below for
  90.                  * definitions. */
  91. } Message;
  92.  
  93. /*
  94.  * Flag bits for messages:
  95.  *
  96.  * REDRAW_PENDING:        Non-zero means a DoWhenIdle handler
  97.  *                has already been queued to redraw
  98.  *                this window.
  99.  * CLEAR_NEEDED;        Need to clear the window when redrawing.
  100.  * GOT_FOCUS:            Non-zero means this button currently
  101.  *                has the input focus.
  102.  */
  103.  
  104. #define REDRAW_PENDING        1
  105. #define CLEAR_NEEDED        2
  106. #define GOT_FOCUS        4
  107.  
  108. /*
  109.  * Information used for argv parsing.
  110.  */
  111.  
  112. static Tk_ConfigSpec configSpecs[] = {
  113.     {TK_CONFIG_ANCHOR, "-anchor", "anchor", "Anchor",
  114.     DEF_MESSAGE_ANCHOR, Tk_Offset(Message, anchor), 0},
  115.     {TK_CONFIG_INT, "-aspect", "aspect", "Aspect",
  116.     DEF_MESSAGE_ASPECT, Tk_Offset(Message, aspect), 0},
  117.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  118.     DEF_MESSAGE_BG_COLOR, Tk_Offset(Message, border),
  119.     TK_CONFIG_COLOR_ONLY},
  120.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  121.     DEF_MESSAGE_BG_MONO, Tk_Offset(Message, border),
  122.     TK_CONFIG_MONO_ONLY},
  123.     {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
  124.     (char *) NULL, 0, 0},
  125.     {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
  126.     (char *) NULL, 0, 0},
  127.     {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
  128.     DEF_MESSAGE_BORDER_WIDTH, Tk_Offset(Message, borderWidth), 0},
  129.     {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
  130.     DEF_MESSAGE_CURSOR, Tk_Offset(Message, cursor), TK_CONFIG_NULL_OK},
  131.     {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
  132.     (char *) NULL, 0, 0},
  133.     {TK_CONFIG_FONT, "-font", "font", "Font",
  134.     DEF_MESSAGE_FONT, Tk_Offset(Message, fontPtr), 0},
  135.     {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
  136.     DEF_MESSAGE_FG, Tk_Offset(Message, fgColorPtr), 0},
  137.     {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground",
  138.     "HighlightBackground", DEF_MESSAGE_HIGHLIGHT_BG,
  139.     Tk_Offset(Message, highlightBgColorPtr), 0},
  140.     {TK_CONFIG_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
  141.     DEF_MESSAGE_HIGHLIGHT, Tk_Offset(Message, highlightColorPtr), 0},
  142.     {TK_CONFIG_PIXELS, "-highlightthickness", "highlightThickness",
  143.     "HighlightThickness",
  144.     DEF_MESSAGE_HIGHLIGHT_WIDTH, Tk_Offset(Message, highlightWidth), 0},
  145.     {TK_CONFIG_JUSTIFY, "-justify", "justify", "Justify",
  146.     DEF_MESSAGE_JUSTIFY, Tk_Offset(Message, justify), 0},
  147.     {TK_CONFIG_PIXELS, "-padx", "padX", "Pad",
  148.     DEF_MESSAGE_PADX, Tk_Offset(Message, padX), 0},
  149.     {TK_CONFIG_PIXELS, "-pady", "padY", "Pad",
  150.     DEF_MESSAGE_PADY, Tk_Offset(Message, padY), 0},
  151.     {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
  152.     DEF_MESSAGE_RELIEF, Tk_Offset(Message, relief), 0},
  153.     {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus",
  154.     DEF_MESSAGE_TAKE_FOCUS, Tk_Offset(Message, takeFocus),
  155.     TK_CONFIG_NULL_OK},
  156.     {TK_CONFIG_STRING, "-text", "text", "Text",
  157.     DEF_MESSAGE_TEXT, Tk_Offset(Message, string), 0},
  158.     {TK_CONFIG_STRING, "-textvariable", "textVariable", "Variable",
  159.     DEF_MESSAGE_TEXT_VARIABLE, Tk_Offset(Message, textVarName),
  160.     TK_CONFIG_NULL_OK},
  161.     {TK_CONFIG_PIXELS, "-width", "width", "Width",
  162.     DEF_MESSAGE_WIDTH, Tk_Offset(Message, width), 0},
  163.     {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
  164.     (char *) NULL, 0, 0}
  165. };
  166.  
  167. /*
  168.  * Forward declarations for procedures defined later in this file:
  169.  */
  170.  
  171. static void        MessageCmdDeletedProc _ANSI_ARGS_((
  172.                 ClientData clientData));
  173. static void        MessageEventProc _ANSI_ARGS_((ClientData clientData,
  174.                 XEvent *eventPtr));
  175. static char *        MessageTextVarProc _ANSI_ARGS_((ClientData clientData,
  176.                 Tcl_Interp *interp, char *name1, char *name2,
  177.                 int flags));
  178. static int        MessageWidgetCmd _ANSI_ARGS_((ClientData clientData,
  179.                 Tcl_Interp *interp, int argc, char **argv));
  180. static void        ComputeMessageGeometry _ANSI_ARGS_((Message *msgPtr));
  181. static int        ConfigureMessage _ANSI_ARGS_((Tcl_Interp *interp,
  182.                 Message *msgPtr, int argc, char **argv,
  183.                 int flags));
  184. static void        DestroyMessage _ANSI_ARGS_((ClientData clientData));
  185. static void        DisplayMessage _ANSI_ARGS_((ClientData clientData));
  186.  
  187. /*
  188.  *--------------------------------------------------------------
  189.  *
  190.  * Tk_MessageCmd --
  191.  *
  192.  *    This procedure is invoked to process the "message" Tcl
  193.  *    command.  See the user documentation for details on what
  194.  *    it does.
  195.  *
  196.  * Results:
  197.  *    A standard Tcl result.
  198.  *
  199.  * Side effects:
  200.  *    See the user documentation.
  201.  *
  202.  *--------------------------------------------------------------
  203.  */
  204.  
  205. int
  206. Tk_MessageCmd(clientData, interp, argc, argv)
  207.     ClientData clientData;    /* Main window associated with
  208.                  * interpreter. */
  209.     Tcl_Interp *interp;        /* Current interpreter. */
  210.     int argc;            /* Number of arguments. */
  211.     char **argv;        /* Argument strings. */
  212. {
  213.     register Message *msgPtr;
  214.     Tk_Window new;
  215.     Tk_Window tkwin = (Tk_Window) clientData;
  216.  
  217.     if (argc < 2) {
  218.     Tcl_AppendResult(interp, "wrong # args:  should be \"",
  219.         argv[0], " pathName ?options?\"", (char *) NULL);
  220.     return TCL_ERROR;
  221.     }
  222.  
  223.     new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL);
  224.     if (new == NULL) {
  225.     return TCL_ERROR;
  226.     }
  227.  
  228.     msgPtr = (Message *) ckalloc(sizeof(Message));
  229.     msgPtr->tkwin = new;
  230.     msgPtr->display = Tk_Display(new);
  231.     msgPtr->interp = interp;
  232.     msgPtr->widgetCmd = Tcl_CreateCommand(interp, Tk_PathName(msgPtr->tkwin),
  233.         MessageWidgetCmd, (ClientData) msgPtr, MessageCmdDeletedProc);
  234.     msgPtr->string = NULL;
  235.     msgPtr->numChars = 0;
  236.     msgPtr->textVarName = NULL;
  237.     msgPtr->border = NULL;
  238.     msgPtr->borderWidth = 0;
  239.     msgPtr->relief = TK_RELIEF_FLAT;
  240.     msgPtr->highlightWidth = 0;
  241.     msgPtr->highlightBgColorPtr = NULL;
  242.     msgPtr->highlightColorPtr = NULL;
  243.     msgPtr->inset = 0;
  244.     msgPtr->fontPtr = NULL;
  245.     msgPtr->fgColorPtr = NULL;
  246.     msgPtr->textGC = None;
  247.     msgPtr->padX = 0;
  248.     msgPtr->padY = 0;
  249.     msgPtr->anchor = TK_ANCHOR_CENTER;
  250.     msgPtr->width = 0;
  251.     msgPtr->aspect = 150;
  252.     msgPtr->lineLength = 0;
  253.     msgPtr->msgHeight = 0;
  254.     msgPtr->justify = TK_JUSTIFY_LEFT;
  255.     msgPtr->cursor = None;
  256.     msgPtr->takeFocus = NULL;
  257.     msgPtr->flags = 0;
  258.  
  259.     Tk_SetClass(msgPtr->tkwin, "Message");
  260.     Tk_CreateEventHandler(msgPtr->tkwin,
  261.         ExposureMask|StructureNotifyMask|FocusChangeMask,
  262.         MessageEventProc, (ClientData) msgPtr);
  263.     if (ConfigureMessage(interp, msgPtr, argc-2, argv+2, 0) != TCL_OK) {
  264.     goto error;
  265.     }
  266.  
  267.     interp->result = Tk_PathName(msgPtr->tkwin);
  268.     return TCL_OK;
  269.  
  270.     error:
  271.     Tk_DestroyWindow(msgPtr->tkwin);
  272.     return TCL_ERROR;
  273. }
  274.  
  275. /*
  276.  *--------------------------------------------------------------
  277.  *
  278.  * MessageWidgetCmd --
  279.  *
  280.  *    This procedure is invoked to process the Tcl command
  281.  *    that corresponds to a widget managed by this module.
  282.  *    See the user documentation for details on what it does.
  283.  *
  284.  * Results:
  285.  *    A standard Tcl result.
  286.  *
  287.  * Side effects:
  288.  *    See the user documentation.
  289.  *
  290.  *--------------------------------------------------------------
  291.  */
  292.  
  293. static int
  294. MessageWidgetCmd(clientData, interp, argc, argv)
  295.     ClientData clientData;    /* Information about message widget. */
  296.     Tcl_Interp *interp;        /* Current interpreter. */
  297.     int argc;            /* Number of arguments. */
  298.     char **argv;        /* Argument strings. */
  299. {
  300.     register Message *msgPtr = (Message *) clientData;
  301.     size_t length;
  302.     int c;
  303.  
  304.     if (argc < 2) {
  305.     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  306.         " option ?arg arg ...?\"", (char *) NULL);
  307.     return TCL_ERROR;
  308.     }
  309.     c = argv[1][0];
  310.     length = strlen(argv[1]);
  311.     if ((c == 'c') && (strncmp(argv[1], "cget", length) == 0)
  312.         && (length >= 2)) {
  313.     if (argc != 3) {
  314.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  315.             argv[0], " cget option\"",
  316.             (char *) NULL);
  317.         return TCL_ERROR;
  318.     }
  319.     return Tk_ConfigureValue(interp, msgPtr->tkwin, configSpecs,
  320.         (char *) msgPtr, argv[2], 0);
  321.     } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
  322.         && (length  >= 2)) {
  323.     if (argc == 2) {
  324.         return Tk_ConfigureInfo(interp, msgPtr->tkwin, configSpecs,
  325.             (char *) msgPtr, (char *) NULL, 0);
  326.     } else if (argc == 3) {
  327.         return Tk_ConfigureInfo(interp, msgPtr->tkwin, configSpecs,
  328.             (char *) msgPtr, argv[2], 0);
  329.     } else {
  330.         return ConfigureMessage(interp, msgPtr, argc-2, argv+2,
  331.             TK_CONFIG_ARGV_ONLY);
  332.     }
  333.     } else {
  334.     Tcl_AppendResult(interp, "bad option \"", argv[1],
  335.         "\":  must be cget or configure", (char *) NULL);
  336.     return TCL_ERROR;
  337.     }
  338. }
  339.  
  340. /*
  341.  *----------------------------------------------------------------------
  342.  *
  343.  * DestroyMessage --
  344.  *
  345.  *    This procedure is invoked by Tk_EventuallyFree or Tk_Release
  346.  *    to clean up the internal structure of a message at a safe time
  347.  *    (when no-one is using it anymore).
  348.  *
  349.  * Results:
  350.  *    None.
  351.  *
  352.  * Side effects:
  353.  *    Everything associated with the message is freed up.
  354.  *
  355.  *----------------------------------------------------------------------
  356.  */
  357.  
  358. static void
  359. DestroyMessage(clientData)
  360.     ClientData clientData;    /* Info about message widget. */
  361. {
  362.     register Message *msgPtr = (Message *) clientData;
  363.  
  364.     /*
  365.      * Free up all the stuff that requires special handling, then
  366.      * let Tk_FreeOptions handle all the standard option-related
  367.      * stuff.
  368.      */
  369.  
  370.     if (msgPtr->textVarName != NULL) {
  371.     Tcl_UntraceVar(msgPtr->interp, msgPtr->textVarName,
  372.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  373.         MessageTextVarProc, (ClientData) msgPtr);
  374.     }
  375.     if (msgPtr->textGC != None) {
  376.     Tk_FreeGC(msgPtr->display, msgPtr->textGC);
  377.     }
  378.     Tk_FreeOptions(configSpecs, (char *) msgPtr, msgPtr->display, 0);
  379.     ckfree((char *) msgPtr);
  380. }
  381.  
  382. /*
  383.  *----------------------------------------------------------------------
  384.  *
  385.  * ConfigureMessage --
  386.  *
  387.  *    This procedure is called to process an argv/argc list, plus
  388.  *    the Tk option database, in order to configure (or
  389.  *    reconfigure) a message widget.
  390.  *
  391.  * Results:
  392.  *    The return value is a standard Tcl result.  If TCL_ERROR is
  393.  *    returned, then interp->result contains an error message.
  394.  *
  395.  * Side effects:
  396.  *    Configuration information, such as text string, colors, font,
  397.  *    etc. get set for msgPtr;  old resources get freed, if there
  398.  *    were any.
  399.  *
  400.  *----------------------------------------------------------------------
  401.  */
  402.  
  403. static int
  404. ConfigureMessage(interp, msgPtr, argc, argv, flags)
  405.     Tcl_Interp *interp;        /* Used for error reporting. */
  406.     register Message *msgPtr;    /* Information about widget;  may or may
  407.                  * not already have values for some fields. */
  408.     int argc;            /* Number of valid entries in argv. */
  409.     char **argv;        /* Arguments. */
  410.     int flags;            /* Flags to pass to Tk_ConfigureWidget. */
  411. {
  412.     XGCValues gcValues;
  413.     GC newGC;
  414.  
  415.     /*
  416.      * Eliminate any existing trace on a variable monitored by the message.
  417.      */
  418.  
  419.     if (msgPtr->textVarName != NULL) {
  420.     Tcl_UntraceVar(interp, msgPtr->textVarName, 
  421.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  422.         MessageTextVarProc, (ClientData) msgPtr);
  423.     }
  424.  
  425.     if (Tk_ConfigureWidget(interp, msgPtr->tkwin, configSpecs,
  426.         argc, argv, (char *) msgPtr, flags) != TCL_OK) {
  427.     return TCL_ERROR;
  428.     }
  429.  
  430.     /*
  431.      * If the message is to display the value of a variable, then set up
  432.      * a trace on the variable's value, create the variable if it doesn't
  433.      * exist, and fetch its current value.
  434.      */
  435.  
  436.     if (msgPtr->textVarName != NULL) {
  437.     char *value;
  438.  
  439.     value = Tcl_GetVar(interp, msgPtr->textVarName, TCL_GLOBAL_ONLY);
  440.     if (value == NULL) {
  441.         Tcl_SetVar(interp, msgPtr->textVarName, msgPtr->string,
  442.             TCL_GLOBAL_ONLY);
  443.     } else {
  444.         if (msgPtr->string != NULL) {
  445.         ckfree(msgPtr->string);
  446.         }
  447.         msgPtr->string = (char *) ckalloc((unsigned) (strlen(value) + 1));
  448.         strcpy(msgPtr->string, value);
  449.     }
  450.     Tcl_TraceVar(interp, msgPtr->textVarName,
  451.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  452.         MessageTextVarProc, (ClientData) msgPtr);
  453.     }
  454.  
  455.     /*
  456.      * A few other options need special processing, such as setting
  457.      * the background from a 3-D border or handling special defaults
  458.      * that couldn't be specified to Tk_ConfigureWidget.
  459.      */
  460.  
  461.     msgPtr->numChars = strlen(msgPtr->string);
  462.  
  463.     Tk_SetBackgroundFromBorder(msgPtr->tkwin, msgPtr->border);
  464.  
  465.     if (msgPtr->highlightWidth < 0) {
  466.     msgPtr->highlightWidth = 0;
  467.     }
  468.  
  469.     gcValues.font = msgPtr->fontPtr->fid;
  470.     gcValues.foreground = msgPtr->fgColorPtr->pixel;
  471.     newGC = Tk_GetGC(msgPtr->tkwin, GCForeground|GCFont,
  472.         &gcValues);
  473.     if (msgPtr->textGC != None) {
  474.     Tk_FreeGC(msgPtr->display, msgPtr->textGC);
  475.     }
  476.     msgPtr->textGC = newGC;
  477.  
  478.     if (msgPtr->padX == -1) {
  479.     msgPtr->padX = msgPtr->fontPtr->ascent/2;
  480.     }
  481.  
  482.     if (msgPtr->padY == -1) {
  483.     msgPtr->padY = msgPtr->fontPtr->ascent/4;
  484.     }
  485.  
  486.     /*
  487.      * Recompute the desired geometry for the window, and arrange for
  488.      * the window to be redisplayed.
  489.      */
  490.  
  491.     ComputeMessageGeometry(msgPtr);
  492.     if ((msgPtr->tkwin != NULL) && Tk_IsMapped(msgPtr->tkwin)
  493.         && !(msgPtr->flags & REDRAW_PENDING)) {
  494.     Tk_DoWhenIdle(DisplayMessage, (ClientData) msgPtr);
  495.     msgPtr->flags |= REDRAW_PENDING|CLEAR_NEEDED;
  496.     }
  497.  
  498.     return TCL_OK;
  499. }
  500.  
  501. /*
  502.  *--------------------------------------------------------------
  503.  *
  504.  * ComputeMessageGeometry --
  505.  *
  506.  *    Compute the desired geometry for a message window,
  507.  *    taking into account the desired aspect ratio for the
  508.  *    window.
  509.  *
  510.  * Results:
  511.  *    None.
  512.  *
  513.  * Side effects:
  514.  *    Tk_GeometryRequest is called to inform the geometry
  515.  *    manager of the desired geometry for this window.
  516.  *
  517.  *--------------------------------------------------------------
  518.  */
  519.  
  520. static void
  521. ComputeMessageGeometry(msgPtr)
  522.     register Message *msgPtr;    /* Information about window. */
  523. {
  524.     char *p;
  525.     int width, inc, height, numLines;
  526.     int thisWidth, maxWidth;
  527.     int aspect, lowerBound, upperBound;
  528.  
  529.     msgPtr->inset = msgPtr->borderWidth + msgPtr->highlightWidth;
  530.  
  531.     /*
  532.      * Compute acceptable bounds for the final aspect ratio.
  533.      */
  534.  
  535.     aspect = msgPtr->aspect/10;
  536.     if (aspect < 5) {
  537.     aspect = 5;
  538.     }
  539.     lowerBound = msgPtr->aspect - aspect;
  540.     upperBound = msgPtr->aspect + aspect;
  541.  
  542.     /*
  543.      * Do the computation in multiple passes:  start off with
  544.      * a very wide window, and compute its height.  Then change
  545.      * the width and try again.  Reduce the size of the change
  546.      * and iterate until dimensions are found that approximate
  547.      * the desired aspect ratio.  Or, if the user gave an explicit
  548.      * width then just use that.
  549.      */
  550.  
  551.     if (msgPtr->width > 0) {
  552.     width = msgPtr->width;
  553.     inc = 0;
  554.     } else {
  555.     width = WidthOfScreen(Tk_Screen(msgPtr->tkwin))/2;
  556.     inc = width/2;
  557.     }
  558.     for ( ; ; inc /= 2) {
  559.     maxWidth = 0;
  560.     for (numLines = 1, p = msgPtr->string; ; numLines++)  {
  561.         if (*p == '\n') {
  562.         p++;
  563.         continue;
  564.         }
  565.         p += TkMeasureChars(msgPtr->fontPtr, p,
  566.             msgPtr->numChars - (p - msgPtr->string), 0, width, 0,
  567.             TK_WHOLE_WORDS|TK_AT_LEAST_ONE, &thisWidth);
  568.         if (thisWidth > maxWidth) {
  569.         maxWidth = thisWidth;
  570.         }
  571.         if (*p == 0) {
  572.         break;
  573.         }
  574.  
  575.         /*
  576.          * Skip spaces and tabs at the beginning of a line, unless
  577.          * they follow a user-requested newline.
  578.          */
  579.  
  580.         while (isspace(UCHAR(*p))) {
  581.         if (*p == '\n') {
  582.             p++;
  583.             break;
  584.         }
  585.         p++;
  586.         }
  587.     }
  588.  
  589.     height = numLines * (msgPtr->fontPtr->ascent
  590.         + msgPtr->fontPtr->descent) + 2*msgPtr->inset
  591.         + 2*msgPtr->padY;
  592.     if (inc <= 2) {
  593.         break;
  594.     }
  595.     aspect = (100*(maxWidth + 2*msgPtr->inset + 2*msgPtr->padX))/height;
  596.     if (aspect < lowerBound) {
  597.         width += inc;
  598.     } else if (aspect > upperBound) {
  599.         width -= inc;
  600.     } else {
  601.         break;
  602.     }
  603.     }
  604.     msgPtr->lineLength = maxWidth;
  605.     msgPtr->msgHeight = numLines * (msgPtr->fontPtr->ascent
  606.         + msgPtr->fontPtr->descent);
  607.     Tk_GeometryRequest(msgPtr->tkwin,
  608.         maxWidth + 2*msgPtr->inset + 2*msgPtr->padX, height);
  609.     Tk_SetInternalBorder(msgPtr->tkwin, msgPtr->inset);
  610. }
  611.  
  612. /*
  613.  *--------------------------------------------------------------
  614.  *
  615.  * DisplayMessage --
  616.  *
  617.  *    This procedure redraws the contents of a message window.
  618.  *
  619.  * Results:
  620.  *    None.
  621.  *
  622.  * Side effects:
  623.  *    Information appears on the screen.
  624.  *
  625.  *--------------------------------------------------------------
  626.  */
  627.  
  628. static void
  629. DisplayMessage(clientData)
  630.     ClientData clientData;    /* Information about window. */
  631. {
  632.     register Message *msgPtr = (Message *) clientData;
  633.     register Tk_Window tkwin = msgPtr->tkwin;
  634.     char *p;
  635.     int x, y, lineLength, numChars, charsLeft;
  636.  
  637.     msgPtr->flags &= ~REDRAW_PENDING;
  638.     if ((msgPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
  639.     return;
  640.     }
  641.     if (msgPtr->flags & CLEAR_NEEDED) {
  642.     XClearWindow(msgPtr->display, Tk_WindowId(tkwin));
  643.     msgPtr->flags &= ~CLEAR_NEEDED;
  644.     }
  645.  
  646.     /*
  647.      * Compute starting y-location for message based on message size
  648.      * and anchor option.
  649.      */
  650.  
  651.     switch (msgPtr->anchor) {
  652.     case TK_ANCHOR_NW: case TK_ANCHOR_N: case TK_ANCHOR_NE:
  653.         y = msgPtr->inset + msgPtr->padY;
  654.         break;
  655.     case TK_ANCHOR_W: case TK_ANCHOR_CENTER: case TK_ANCHOR_E:
  656.         y = ((int) (Tk_Height(tkwin) - msgPtr->msgHeight))/2;
  657.         break;
  658.     default:
  659.         y = Tk_Height(tkwin) - msgPtr->inset - msgPtr->padY
  660.             - msgPtr->msgHeight;
  661.         break;
  662.     }
  663.     y += msgPtr->fontPtr->ascent;
  664.  
  665.     /*
  666.      * Work through the string to display one line at a time.
  667.      * Display each line in three steps.  First compute the
  668.      * line's width, then figure out where to display the
  669.      * line to justify it properly, then display the line.
  670.      */
  671.  
  672.     for (p = msgPtr->string, charsLeft = msgPtr->numChars; *p != 0;
  673.         y += msgPtr->fontPtr->ascent + msgPtr->fontPtr->descent) {
  674.     if (*p == '\n') {
  675.         p++;
  676.         charsLeft--;
  677.         continue;
  678.     }
  679.     numChars = TkMeasureChars(msgPtr->fontPtr, p, charsLeft, 0,
  680.         msgPtr->lineLength, 0, TK_WHOLE_WORDS|TK_AT_LEAST_ONE,
  681.         &lineLength);
  682.     switch (msgPtr->anchor) {
  683.         case TK_ANCHOR_NW: case TK_ANCHOR_W: case TK_ANCHOR_SW:
  684.         x = msgPtr->inset + msgPtr->padX;
  685.         break;
  686.         case TK_ANCHOR_N: case TK_ANCHOR_CENTER: case TK_ANCHOR_S:
  687.         x = ((int) (Tk_Width(tkwin) - msgPtr->lineLength))/2;
  688.         break;
  689.         default:
  690.         x = Tk_Width(tkwin) - msgPtr->inset - msgPtr->padX
  691.             - msgPtr->lineLength;
  692.         break;
  693.     }
  694.     if (msgPtr->justify == TK_JUSTIFY_CENTER) {
  695.         x += (msgPtr->lineLength - lineLength)/2;
  696.     } else if (msgPtr->justify == TK_JUSTIFY_RIGHT) {
  697.         x += msgPtr->lineLength - lineLength;
  698.     }
  699.     TkDisplayChars(msgPtr->display, Tk_WindowId(tkwin),
  700.         msgPtr->textGC, msgPtr->fontPtr, p, numChars, x, y, x, 0);
  701.     p += numChars;
  702.     charsLeft -= numChars;
  703.  
  704.     /*
  705.      * Skip blanks at the beginning of a line, unless they follow
  706.      * a user-requested newline.
  707.      */
  708.  
  709.     while (isspace(UCHAR(*p))) {
  710.         charsLeft--;
  711.         if (*p == '\n') {
  712.         p++;
  713.         break;
  714.         }
  715.         p++;
  716.     }
  717.     }
  718.  
  719.     if (msgPtr->relief != TK_RELIEF_FLAT) {
  720.     Tk_Draw3DRectangle(tkwin, Tk_WindowId(tkwin), msgPtr->border,
  721.         msgPtr->highlightWidth, msgPtr->highlightWidth,
  722.         Tk_Width(tkwin) - 2*msgPtr->highlightWidth,
  723.         Tk_Height(tkwin) - 2*msgPtr->highlightWidth,
  724.         msgPtr->borderWidth, msgPtr->relief);
  725.     }
  726.     if (msgPtr->highlightWidth != 0) {
  727.     GC gc;
  728.  
  729.     if (msgPtr->flags & GOT_FOCUS) {
  730.         gc = Tk_GCForColor(msgPtr->highlightColorPtr, Tk_WindowId(tkwin));
  731.     } else {
  732.         gc = Tk_GCForColor(msgPtr->highlightBgColorPtr, Tk_WindowId(tkwin));
  733.     }
  734.     Tk_DrawFocusHighlight(tkwin, gc, msgPtr->highlightWidth,
  735.         Tk_WindowId(tkwin));
  736.     }
  737. }
  738.  
  739. /*
  740.  *--------------------------------------------------------------
  741.  *
  742.  * MessageEventProc --
  743.  *
  744.  *    This procedure is invoked by the Tk dispatcher for various
  745.  *    events on messages.
  746.  *
  747.  * Results:
  748.  *    None.
  749.  *
  750.  * Side effects:
  751.  *    When the window gets deleted, internal structures get
  752.  *    cleaned up.  When it gets exposed, it is redisplayed.
  753.  *
  754.  *--------------------------------------------------------------
  755.  */
  756.  
  757. static void
  758. MessageEventProc(clientData, eventPtr)
  759.     ClientData clientData;    /* Information about window. */
  760.     XEvent *eventPtr;        /* Information about event. */
  761. {
  762.     Message *msgPtr = (Message *) clientData;
  763.  
  764.     if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) {
  765.     goto redraw;
  766.     } else if (eventPtr->type == ConfigureNotify) {
  767.     msgPtr->flags |= CLEAR_NEEDED;
  768.     goto redraw;
  769.     } else if (eventPtr->type == DestroyNotify) {
  770.     if (msgPtr->tkwin != NULL) {
  771.         msgPtr->tkwin = NULL;
  772.         Tcl_DeleteCommand(msgPtr->interp,
  773.             Tcl_GetCommandName(msgPtr->interp, msgPtr->widgetCmd));
  774.     }
  775.     if (msgPtr->flags & REDRAW_PENDING) {
  776.         Tk_CancelIdleCall(DisplayMessage, (ClientData) msgPtr);
  777.     }
  778.     Tk_EventuallyFree((ClientData) msgPtr, DestroyMessage);
  779.     } else if (eventPtr->type == FocusIn) {
  780.     if (eventPtr->xfocus.detail != NotifyInferior) {
  781.         msgPtr->flags |= GOT_FOCUS;
  782.         if (msgPtr->highlightWidth > 0) {
  783.         goto redraw;
  784.         }
  785.     }
  786.     } else if (eventPtr->type == FocusOut) {
  787.     if (eventPtr->xfocus.detail != NotifyInferior) {
  788.         msgPtr->flags &= ~GOT_FOCUS;
  789.         if (msgPtr->highlightWidth > 0) {
  790.         goto redraw;
  791.         }
  792.     }
  793.     }
  794.     return;
  795.  
  796.     redraw:
  797.     if ((msgPtr->tkwin != NULL) && !(msgPtr->flags & REDRAW_PENDING)) {
  798.     Tk_DoWhenIdle(DisplayMessage, (ClientData) msgPtr);
  799.     msgPtr->flags |= REDRAW_PENDING;
  800.     }
  801. }
  802.  
  803. /*
  804.  *----------------------------------------------------------------------
  805.  *
  806.  * MessageCmdDeletedProc --
  807.  *
  808.  *    This procedure is invoked when a widget command is deleted.  If
  809.  *    the widget isn't already in the process of being destroyed,
  810.  *    this command destroys it.
  811.  *
  812.  * Results:
  813.  *    None.
  814.  *
  815.  * Side effects:
  816.  *    The widget is destroyed.
  817.  *
  818.  *----------------------------------------------------------------------
  819.  */
  820.  
  821. static void
  822. MessageCmdDeletedProc(clientData)
  823.     ClientData clientData;    /* Pointer to widget record for widget. */
  824. {
  825.     Message *msgPtr = (Message *) clientData;
  826.     Tk_Window tkwin = msgPtr->tkwin;
  827.  
  828.     /*
  829.      * This procedure could be invoked either because the window was
  830.      * destroyed and the command was then deleted (in which case tkwin
  831.      * is NULL) or because the command was deleted, and then this procedure
  832.      * destroys the widget.
  833.      */
  834.  
  835.     if (tkwin != NULL) {
  836.     msgPtr->tkwin = NULL;
  837.     Tk_DestroyWindow(tkwin);
  838.     }
  839. }
  840.  
  841. /*
  842.  *--------------------------------------------------------------
  843.  *
  844.  * MessageTextVarProc --
  845.  *
  846.  *    This procedure is invoked when someone changes the variable
  847.  *    whose contents are to be displayed in a message.
  848.  *
  849.  * Results:
  850.  *    NULL is always returned.
  851.  *
  852.  * Side effects:
  853.  *    The text displayed in the message will change to match the
  854.  *    variable.
  855.  *
  856.  *--------------------------------------------------------------
  857.  */
  858.  
  859.     /* ARGSUSED */
  860. static char *
  861. MessageTextVarProc(clientData, interp, name1, name2, flags)
  862.     ClientData clientData;    /* Information about message. */
  863.     Tcl_Interp *interp;        /* Interpreter containing variable. */
  864.     char *name1;        /* Name of variable. */
  865.     char *name2;        /* Second part of variable name. */
  866.     int flags;            /* Information about what happened. */
  867. {
  868.     register Message *msgPtr = (Message *) clientData;
  869.     char *value;
  870.  
  871.     /*
  872.      * If the variable is unset, then immediately recreate it unless
  873.      * the whole interpreter is going away.
  874.      */
  875.  
  876.     if (flags & TCL_TRACE_UNSETS) {
  877.     if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
  878.         Tcl_SetVar(interp, msgPtr->textVarName, msgPtr->string,
  879.             TCL_GLOBAL_ONLY);
  880.         Tcl_TraceVar(interp, msgPtr->textVarName,
  881.             TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  882.             MessageTextVarProc, clientData);
  883.     }
  884.     return (char *) NULL;
  885.     }
  886.  
  887.     value = Tcl_GetVar(interp, msgPtr->textVarName, TCL_GLOBAL_ONLY);
  888.     if (value == NULL) {
  889.     value = "";
  890.     }
  891.     if (msgPtr->string != NULL) {
  892.     ckfree(msgPtr->string);
  893.     }
  894.     msgPtr->numChars = strlen(value);
  895.     msgPtr->string = (char *) ckalloc((unsigned) (msgPtr->numChars + 1));
  896.     strcpy(msgPtr->string, value);
  897.     ComputeMessageGeometry(msgPtr);
  898.  
  899.     msgPtr->flags |= CLEAR_NEEDED;
  900.     if ((msgPtr->tkwin != NULL) && Tk_IsMapped(msgPtr->tkwin)
  901.         && !(msgPtr->flags & REDRAW_PENDING)) {
  902.     Tk_DoWhenIdle(DisplayMessage, (ClientData) msgPtr);
  903.     msgPtr->flags |= REDRAW_PENDING;
  904.     }
  905.     return (char *) NULL;
  906. }
  907.